package fons.navigator.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.SwingWorker;
import javax.swing.border.BevelBorder;

import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import fons.model.ontology.Context;
import fons.model.ontology.interfaces.DecisionMaker;
import fons.navigator.load.ContextBuilder;
import fons.navigator.load.plugin.DataConverter;
import fons.navigator.ui.actions.ActionDescription;
import fons.navigator.ui.actions.ConvertAction;
import fons.navigator.util.SwingUtils;
import fons.navigator.views.ViewConfig;

public class NavigatorMainFrame extends JFrame {
	public static final String APP_NAME = "FoNS-Navigator";
	private static final long serialVersionUID = 1L;
	private static final Cursor WAIT_CURSOR = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
	private static final Cursor NORMAL_CURSOR = Cursor.getDefaultCursor();
	public static final String CONTEXT_CHANGED = "ContextChangedEvent";
	private static final String ACTIVE_VIEW_CHANGED = "ViewChangedEvent";

	private final JDesktopPane desktopPane;
	private final JLabel statusLabel;

	private JInternalFrame activeFrame;
	private Context activeContext;
	private DecisionMaker activeView;

	public NavigatorMainFrame() {
		setTitle("Navigator");

		setJMenuBar(initMenu());

		desktopPane = new JDesktopPane();
		desktopPane.setBackground(Color.gray);
		add(desktopPane, BorderLayout.CENTER);
		setExtendedState(MAXIMIZED_BOTH);

		Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();

		setSize(new Dimension((int)(screen.width * .8), (int)(screen.height * .8)));
		setMinimumSize(new Dimension(800, 600));
		SwingUtils.centerOnScreen(this);

		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				exit();
			}
			@Override
			public void windowOpened(WindowEvent e) {
				//InternalFrameUtils.tile(desktopPane);
			}
		});

		statusLabel = new JLabel("Bereit");
		statusLabel.setFont(new Font("Arial", Font.PLAIN, 12));
		statusLabel.setPreferredSize(new Dimension(getWidth(), 20));
		statusLabel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
		add(statusLabel, BorderLayout.SOUTH);
	}


	public void centerOnScreen(JFrame frame) {
		final Dimension size = frame.getSize();
		final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
		final int locX = (screen.width - size.width) / 2;
		final int locY = (screen.height - size.height) / 2;
		frame.setLocation(locX, locY);
	}

	private JMenuBar initMenu() {
		final JMenuBar menu = new JMenuBar();
		final Map<String, JMenu> menus = Maps.newHashMap();

		List<Class<? extends AbstractAction>> menuEntries = Lists.newArrayList();

		Reflections reflections = new Reflections("fons.navigator.ui.actions", new SubTypesScanner(true));
		for (Class<? extends AbstractAction> actionClass: reflections.getSubTypesOf(AbstractAction.class)) {
			ActionDescription metaInfo = actionClass.getAnnotation(ActionDescription.class);
			if (metaInfo == null) continue;
			menuEntries.add(actionClass);
		}

		Collections.sort(menuEntries, new Comparator<Class<? extends AbstractAction>>() {
			@Override
			public int compare(Class<? extends AbstractAction> arg0,
					Class<? extends AbstractAction> arg1) {
				return
						arg0.getAnnotation(ActionDescription.class).priority() 
						< arg1.getAnnotation(ActionDescription.class).priority() ?
								-1 : 1;
			}

		});

		for (Class<? extends AbstractAction> actionClass: menuEntries) {
			ActionDescription metaInfo = actionClass.getAnnotation(ActionDescription.class);

			try {
				AbstractAction action = actionClass.getConstructor(NavigatorMainFrame.class).newInstance(this);

				if (action.getValue(Action.NAME) == null) {
					action.putValue(Action.NAME, metaInfo.name());
				} 

				if (action.getValue(Action.SHORT_DESCRIPTION) == null) {
					action.putValue(Action.SHORT_DESCRIPTION, metaInfo.description());
				}
				if (action.getValue(Action.SMALL_ICON) == null && !metaInfo.icon().isEmpty()) {
					action.putValue(Action.SMALL_ICON, new ImageIcon(action.getClass().getResource(metaInfo.icon())));
				}

				if (menus.containsKey(metaInfo.menu())) {
					menus.get(metaInfo.menu()).add(new JMenuItem(action));
				} else {
					JMenu newMenu = new JMenu(metaInfo.menu());
					newMenu.add(new JMenuItem(action));
					menus.put(metaInfo.menu(), newMenu);
				}
			} catch (Exception ex) { }
		}

		JMenu convert = new JMenu("Konvertieren");
		convert.add(createConverterMenu());
		if (menus.containsKey("Daten")) {
			menus.get("Daten").add(convert);
		} else {
			menus.put("Daten", convert);
		}

		for (String menuName: Lists.newArrayList("Datei", "Ansicht", "Ontologie", "Daten", "Fenster")) {
			if (menus.containsKey(menuName)) {
				menu.add(menus.get(menuName));
			}
			menus.remove(menuName);
		}

		for (String menuName: menus.keySet()) {
			menu.add(menus.get(menuName));
		}

		return menu;
	}


	public void setActiveFrame(JInternalFrame view) {
		this.activeFrame = view;
	}
	
	public JInternalFrame getActiveFrame() {
		return activeFrame;
	}


	private JMenu createConverterMenu() {
		JMenu convert = new JMenu("Konvertieren");
		Reflections ref = new Reflections("fons.navigator.load.plugins.converters");
		for (Class<? extends DataConverter> importerClass: ref.getSubTypesOf(DataConverter.class)) {
			convert.add(new JMenuItem(new ConvertAction(this, importerClass)));			
		}
		return convert;
	}


	public void shutdown() {
		System.exit(0);
	}

	public boolean confirmExit() {
		if (desktopPane.getAllFrames().length > 0) {
			return true;
		}
		return true;
	}

	private void exit() {
		if (confirmExit()) {
			dispose();
			shutdown();
		}
	}

	public JDesktopPane getDesktopPane() {
		return desktopPane;
	}

	public void load(final String absolutePath) throws Exception {
		SwingWorker<Context, Object> worker = new SwingWorker<Context, Object>() {
			@Override
			protected Context doInBackground() throws Exception {
				setCursor(WAIT_CURSOR);
				return ContextBuilder.parseConfig(ViewConfig.load(absolutePath));
			}

			@Override
			protected void done() {
				setStatusMessage("Bereit");
				setCursor(NORMAL_CURSOR);
				try {
					setActiveContext(get());					
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};
		worker.execute();
	}

	public Context getActiveContext() {
		return activeContext;
	}

	public void resetStatusMessage() {
		if (getActiveContext() != null) {
			setStatusMessage("Bereit. Aktueller Kontext: " + getActiveContext().getName());
		} else {
			setStatusMessage("Bereit.");
		}
	}

	public void setActiveContext(Context context) {
		final Context oldContext = this.activeContext;
		this.activeContext = context;
		if (context != null) {
			setStatusMessage("Aktueller Kontext: " + context.getName());
		}
		NavigatorMainFrame.this.firePropertyChange(CONTEXT_CHANGED, oldContext, getActiveContext());
	}

	public DecisionMaker getActiveView() {
		return activeView;
	}

	public void setActiveView(DecisionMaker activeView) {
		final DecisionMaker oldView = this.activeView;
		this.activeView = activeView;
		this.firePropertyChange(ACTIVE_VIEW_CHANGED, oldView, activeView);
	}

	public void setStatusMessage(String message) {
		this.statusLabel.setText(message);
	}

	public String getStatusMessage() {
		return this.statusLabel.getText();
	}
}
